#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "ADD_SUB.h"
#include "PF_UTIL.h"
#include "simparameters.h"

FPOINT_PTR Add(FPOINT *Mx, FPOINT *My, int Operation) {
    FPOINT_PTR temp;
    FPOINT_PTR FP_adder_resiult;
    char OUT_RESULT[BIT_WIDTH+1];
    char out_sign[2];
    char * out_EX;
    char * out_FR;
    //char FractionX_forAdd[FRACTION_WIDTH+5];
    char * FractionX_forAdd;
    char * TwoCom_Add_result;
    char Add_result_for_shift[FRACTION_WIDTH+5];
    char FractionY_forAdd[FRACTION_WIDTH + 5];
    char Mx_Before_Rshift[FRACTION_WIDTH + 2];
    int exponent_diff;
    int Eff_OF;
    int LeadZeros;

    //SRART Determining the sign of the result
    int compare_result = Compare(Mx, My);

    printf("Compare result is: %d\n", compare_result);
    if (Operation == 0) {
        if (Mx->sign == My->sign) {
            Eff_OF = 0;
        } else {
            Eff_OF = 1;
        }

        if (compare_result == 0) {
            out_sign[0] = Mx->sign;
        } else {
            out_sign[0] = My->sign;
        }
    } else {
        if (Mx->sign == My->sign) {
            Eff_OF = 1;
        } else {
            Eff_OF = 0;
        }

        if (compare_result == 0) {
            out_sign[0] = Mx->sign;
        } else {
            out_sign[0] = -My->sign;
        }
    }
    out_sign[1]='\0';
    //END Determining the sign of the result
    printf("Before swapping: \n");
    printf("Mx's fraction is: %s\n", Mx->Fraction);
    printf("My's fraction is: %s\n", My->Fraction);
    //START checking for is the 2 number need to be swapped
    if (compare_result == 0) {
        temp = Mx;
        Mx = My;
        My = temp;
    }
    //END checking for is the 2 number need to be swapped
    //So, right now, from the magnitude perspective,
    //Mx =< My
    printf("After swapping: \n");
    printf("Mx's fraction is: %s\n", Mx->Fraction);
    printf("My's fraction is: %s\n", My->Fraction);
    //START Shifting Mx.
    if (Mx->Exponent_INT == 0) {
        //That means the number is a under flow
        strcpy(Mx_Before_Rshift, "0");
        strcat(Mx_Before_Rshift, Mx->Fraction);
    } else {
        //That means the number is not a under flow
        strcpy(Mx_Before_Rshift, "1");
        strcat(Mx_Before_Rshift, Mx->Fraction);
    }
    //printf("Mx_Before_Rshift: %s\n",Mx_Before_Rshift);
    exponent_diff = My->Exponent_INT - Mx->Exponent_INT;
    FractionX_forAdd = Rshift(Mx_Before_Rshift, FRACTION_WIDTH + 1, exponent_diff);
    if (My->Exponent_INT == 0) {
        strcpy(FractionY_forAdd, "0");
    } else {
        strcpy(FractionY_forAdd, "1");
    }
    strcat(FractionY_forAdd, My->Fraction);
    strcat(FractionY_forAdd, "000");
    //END Shifting Mx.
    //Now they are ready to add
    printf("Before actual adding, the EOP is: %d\n", Eff_OF);
    printf("FractionX_forAdd: %s\n", FractionX_forAdd);
    printf("FractionY_forAdd: %s\n", FractionY_forAdd);
    //START 2's Complement Adder
    //This rresult will contain FRACTION_WIDTH+5 digits,
    //2 of them before decimal point
    //5 bits serve for the rounding
    TwoCom_Add_result = TwosComplementADDER(FractionX_forAdd, FractionY_forAdd, Eff_OF, FRACTION_WIDTH + 4);
    printf("After 2's complement adding\n");
    printf("TwoCom_Add_result: %s\n", TwoCom_Add_result);
    //END 2's Complement Adder
    
    //Add_result_for_shift will be the input to the following steps
    strncpy (Add_result_for_shift,TwoCom_Add_result,FRACTION_WIDTH+5);
    Add_result_for_shift[FRACTION_WIDTH+5]='\0';
    if(Mx->Exponent_INT==0&&My->Exponent_INT==0&&Add_result_for_shift[1]=='0'){
        LeadZeros=0;
    }else{
        LeadZeros=LeadingZero(Add_result_for_shift);
    }

    //START getting the Exponent
    printf("Mx->Exponent_INT: %d\n", Mx->Exponent_INT);
    printf("My->Exponent_INT: %d\n", My->Exponent_INT);
    if(Mx->Exponent_INT>My->Exponent_INT){
        out_EX=ExponentUpdate(Mx->Exponent,LeadZeros-1);
    }else{
        out_EX=ExponentUpdate(My->Exponent,LeadZeros-1);
    }
    //END getting the Exponent

    //START getting the Fraction
    out_FR=Lshift(Add_result_for_shift,FRACTION_WIDTH+4,LeadZeros-2);
    //END getting the Fraction

    strcpy (OUT_RESULT,out_sign);
    strcat (OUT_RESULT,out_EX);
    strcat (OUT_RESULT,out_FR);

    FP_adder_resiult=CreateFP(OUT_RESULT);
    printf("Final Added Result: \n");
    printf("%s\n", OUT_RESULT);

    return FP_adder_resiult;
}

char * Flip_Bits(int M_in[], int size) {
    char *Flip_out = (char *) malloc(sizeof (char) *(size + 1));
    int i;
    printf("Before Flipping:  %s\n",M_in);
    for (i = 0; i < size; i++) {
        if (M_in[i] == '0') {
            Flip_out[i] = 49;
        } else {
            
            Flip_out[i] = 48;
        }
    }
    Flip_out[size]='\0';
    printf("After Flipping:   %s\n",Flip_out);
    return Flip_out;
}

//Module: Two's Complement Adder
//supposingly, the size here should be FRACTION_WIDTH+4

char * TwosComplementADDER(char Mx[], char My[], char carry_in, int size) {
    char carry = carry_in;
    int i, j;
    char temp_bit_sum;
    char extent_Mx[size + 2];
    char extent_My[size + 2];
    char *out = (char *) malloc(sizeof (char) *(size + 2));

    //since we know both Mx and My will be positive,
    //before we put them into the adder, we will do a positive sign extension
    extent_Mx[0] = 48;
    extent_My[0] = 48;

    if (carry_in == 1) {
        //if so, flip all the bits of Mx[]
        printf("Before Flipping:  %s\n",Mx);
        for (i = 0; i < size; i++) {
            if (Mx[i] == '0') {
                Mx[i] = 49;
            } else {

                Mx[i] = 48;
            }
        }
        printf("After Flipping:   %s\n",Mx);
        //Mx=Flip_Bits(Mx, size);
    }
    
    for (i = size - 1; i >= 0; i--) {
        temp_bit_sum = (Mx[i] - 48)+(My[i] - 48) + carry;
        switch (temp_bit_sum) {
            case 0:
                out[i + 1] = 48;
                carry = 0;
                break;
            case 1:
                out[i + 1] = 49;
                carry = 0;
                break;
            case 2:
                out[i + 1] = 48;
                carry = 1;
                break;
            case 3:
                out[i + 1] = 49;
                carry = 1;
                break;
        }
    }
    if (carry == carry_in) {
        out[0] = 48;
    } else {
        out[0] = 49;
    }

    out[size + 1] = '\0';
    return out;
}

char * Rshift(char Mx[], int size, int distance) {
    char *ShiftResult = (char *) malloc(sizeof (char) *(size + 4));
    char StickySum = 0;
    int i, j;
    for (i = 0; i < distance; i++) {
        ShiftResult[i] = 48;
    }

    for (j = 0; j < size; j++) {
        if ((j + distance)<(size + 2)) {
            ShiftResult[j + distance] = Mx[j];
        } else {
            StickySum += (Mx[j] - 48);
        }
    }
    for (j = size + distance; j < size + 2; j++) {
        ShiftResult[j] = 48;
    }
    if (StickySum > 0) {
        ShiftResult[size + 2] = 49;
    } else {
        ShiftResult[size + 2] = 48;
    }
    ShiftResult[size + 3] = '\0';
    return ShiftResult;
}

int LeadingZero(char inChar[]) {
    int i = 0;
    while (inChar[i] != '\0') {
        if (inChar[i] == '0') {
            i++;
        } else {
            break;
        }
    }

    return i;
}

char * Lshift(char Mx[], int size, int distance) {
    int Shifting_d;
    int i;
    char *ShiftResult = (char *) malloc(sizeof (char) *(FRACTION_WIDTH + 1));

    if (distance < 0) {
        //in this case, it will be a Right shift,   ShiftResult[0]-->Mx[3]
        //in our implementation, it can only be 1 or 2
        Shifting_d = -distance;
        for (i = 0; i < FRACTION_WIDTH; i++) {
            ShiftResult[i] = Mx[i + 3 - Shifting_d];
        }
    } else {
        Shifting_d = distance;
        for (i = 0; i < FRACTION_WIDTH - Shifting_d; i++) {
            ShiftResult[i] = Mx[i + 3 + Shifting_d];
        }
        for (i = FRACTION_WIDTH - Shifting_d; i < FRACTION_WIDTH; i++) {
            ShiftResult[i] = '0';
        }

    }
    ShiftResult[FRACTION_WIDTH] = '\0';

    return ShiftResult;
}

char * ExponentUpdate(char ex[], int lz) {
    char *exponent;
    int exponent_int;
    int inEXint = binary2INT(ex, EXPONENT_WIDTH);
    if(inEXint<=lz){
        exponent_int=0;
    }else{
        exponent_int = inEXint - lz;
    }
    exponent = INT2binary(exponent_int, EXPONENT_WIDTH);

    return exponent;
}
//returns 0 if Mx>=My, 1 if Mx<My

int Compare(FPOINT *Mx, FPOINT *My) {
    int result;
    int Mx_Ex, My_Ex, Mx_Fra, My_Fra;
    Mx_Ex = binary2INT(Mx->Exponent, EXPONENT_WIDTH);
    My_Ex = binary2INT(My->Exponent, EXPONENT_WIDTH);
    Mx_Fra = binary2INT(Mx->Fraction, FRACTION_WIDTH);
    My_Fra = binary2INT(My->Fraction, FRACTION_WIDTH);
    printf("Mx_Ex=%d\n", Mx_Ex);
    printf("My_Ex=%d\n", My_Ex);
    printf("Mx_Fra=%d\n", Mx_Fra);
    printf("My_Fra=%d\n", My_Fra);
    if (Mx_Ex > My_Ex) {
        result = 0;
    } else if (Mx_Ex < My_Ex) {
        result = 1;
    } else {
        if (Mx_Fra >= My_Fra) {
            result = 0;
        } else {
            result = 1;
        }
    }
    return result;
}